#include <rtthread.h>

#include <dfs.h>
#include <dfs_fs.h>
#include <dfs_file.h>

#include <string.h>

#include "procfs.h"

struct thread_file
{
    uint32_t size;
    uint16_t curline;
    uint16_t index;
    char line[72];
};

#define FILE_TYPE struct thread_file

static void thdinfo_sprintf(FILE_TYPE *f, struct rt_thread *thread)
{
    uint8_t stat;
    uint8_t *ptr;
    int maxlen = RT_NAME_MAX;

#ifdef RT_USING_SMP
    if (thread->oncpu != RT_CPU_DETACHED)
        rt_sprintf("%-*.*s %3d %3d ", maxlen, RT_NAME_MAX, thread->name, thread->oncpu, thread->current_priority);
    else
        rt_sprintf("%-*.*s N/A %3d ", maxlen, RT_NAME_MAX, thread->name, thread->current_priority);
#else
    rt_sprintf(f->line, "%-*.*s %3d ", maxlen, RT_NAME_MAX, thread->name, thread->current_priority);
#endif /*RT_USING_SMP*/

    switch (thread->stat & RT_THREAD_STAT_MASK)
    {
    case RT_THREAD_READY:
        strcat(f->line, " ready  ");
        break;
    case RT_THREAD_SUSPEND:
        strcat(f->line, " suspend");
        break;
    case RT_THREAD_INIT:
        strcat(f->line, " init   ");
        break;
    case RT_THREAD_CLOSE:
        strcat(f->line, " close  ");
        break;
    case RT_THREAD_RUNNING:
        strcat(f->line, " running");
        break;
    }

    f->size = rt_strlen(f->line);
    ptr = (uint8_t *)thread->stack_addr;

#if defined(ARCH_CPU_STACK_GROWS_UPWARD)
    ptr += (thread->stack_size - 1);
    while (*ptr == '#')
        ptr--;

    f->size += rt_sprintf(&f->line[f->size], " 0x%08x 0x%08x    %02d%%   0x%08x %03d\n",
               ((rt_ubase_t)thread->sp - (rt_ubase_t)thread->stack_addr),
               thread->stack_size,
               ((rt_ubase_t)ptr - (rt_ubase_t)thread->stack_addr) * 100 / thread->stack_size,
               thread->remaining_tick,
               thread->error);
#else
    while (*ptr == '#')
        ptr++;

    f->size += rt_sprintf(&f->line[f->size], " 0x%08x 0x%08x    %02d%%   0x%08x %03d\n",
               thread->stack_size + ((rt_ubase_t)thread->stack_addr - (rt_ubase_t)thread->sp),
               thread->stack_size,
               (thread->stack_size - ((rt_ubase_t)ptr - (rt_ubase_t)thread->stack_addr)) * 100 / thread->stack_size,
               thread->remaining_tick,
               thread->error);
#endif
}

static int next_thread(FILE_TYPE *f, struct rt_thread *t)
{
    struct rt_object_information *info;
    struct rt_thread *pos, *n;
    struct rt_list_node *node;
    uint16_t ind = 0, last;

    info = rt_object_get_information(RT_Object_Class_Thread);

    last = f->index;
    rt_list_for_each_entry_safe(pos, n, struct rt_thread, &info->object_list, list)
    {
        if (ind++ < f->index)
            continue;

        *t = *pos;
        f->index = ind;
        break;
    }

    return (last < f->index);
}

static int readfile(FILE_TYPE *f)
{
    switch (f->curline)
    {
    case 0:
    {
        int maxlen = RT_NAME_MAX;

        f->size = rt_sprintf(f->line, "%-*.s pri  status      sp     stack-size max-used left-tick  error\n", maxlen, "name");
        f->curline = 1;
    }
    break;
    case 1:
    {
        struct rt_thread t;

        if (next_thread(f, &t))
        {
            thdinfo_sprintf(f, &t);
        }
        else
        {
            f->size = 0;
        }
    }
    break;
    }

    return 0;
}

static int dfs_procfs_open(struct dfs_fd *file)
{
    FILE_TYPE *f;

    f = (FILE_TYPE *)rt_calloc(1, sizeof(FILE_TYPE));
    if (!f)
        return -ENOMEM;

    file->data = f;

    return 0;
}

static int dfs_procfs_close(struct dfs_fd *file)
{
    rt_free(file->data);

    return 0;
}

static int dfs_procfs_read(struct dfs_fd *file, void *buf, size_t count)
{
    int ret;
    FILE_TYPE *f;
    size_t cnt, pos;

    f = (FILE_TYPE *)file->data;

    if (file->pos == 0)
    {
        readfile(f);
    }

    if (f->size == 0)
        return 0;

    pos = file->pos;
    cnt = (count > f->size) ? f->size : count;
    rt_memcpy(buf, &f->line[pos], cnt);
    file->pos += cnt;

    if (file->pos >= f->size)
    {
        file->pos = 0;
    }

    ret = cnt;

    return ret;
}

const struct dfs_file_ops _thread_fops =
{
    dfs_procfs_open,
    dfs_procfs_close,
    RT_NULL,
    dfs_procfs_read,
    RT_NULL,
    RT_NULL,
    RT_NULL,
    RT_NULL,
    RT_NULL
};
